/*
 * Licensed to the 南京汇海交通科技有限公司
 *
 * Copyright (c) 2025.
 * All rights reserved.
 *
 * 本软件属南京汇海交通科技有限公司所有，在未获得南京汇海交通科技有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.chframework.cloud.web.openfeign;

import com.chframework.cloud.core.openfeign.FeignClientAdvice;
import com.google.common.collect.ImmutableList;
import com.chframework.cloud.core.properties.FeignHeaderProperty;
import com.chframework.common.constant.ApplicationConstant;
import com.chframework.common.extend.ServletHelper;
import com.chframework.common.thread.threadlocal.TransmittableThreadLocalHelper;
import feign.Request;
import feign.RequestTemplate;
import feign.Response;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * Servlet环境下
 * 默认实现FeignClient请求与响应两个阶段的回调
 */
@Slf4j
public final class ServletFeignClientAdvice implements FeignClientAdvice {

    private FeignHeaderProperty feignHeaderProperty;

    public ServletFeignClientAdvice(FeignHeaderProperty feignHeaderProperty) {
        this.feignHeaderProperty = feignHeaderProperty;
    }

    @Override
    public void beforeAdvice(RequestTemplate requestTemplate) {
        String url = requestTemplate.url();
        try {
            HttpServletRequest request = ServletHelper.getRequest();

            // 注意，此处不该使用异步重写请求头，否则会导致请求头参数传递失败的情况发生
            // 获取 在FeignClient调用过程中 需要被传递 的 HTTP请求头参数
            for (String name : feignHeaderProperty.getHeaders()) {
                // 请求头中通常传递session-id或request-id之类的特殊参数
                String value = request.getHeader(name);
                if (StringUtils.isBlank(value) && ApplicationConstant.DEFAULT_TOKEN_KEY.equals(name)) {
                    // 基于Shiro安全框架的分布式会话管理方案，它的session-id只能通过url传递，此处需要适配一下
                    value = request.getParameter(name);
                }
                if (StringUtils.isEmpty(value)) {
                    // 如果请求头当中没有该参数，极有可能是希望将主线程的工作内存或ThreadLocal中的参数传递到微服务中，
                    // 就例如我们的request-id在过滤器中会优先从请求头中获取，如果请求头中不存在则主动生成一个并保存在ThreadLocal中。
                    value = TransmittableThreadLocalHelper.getEnvironment().getOrDefault(name, StringUtils.EMPTY);
                }
                if (StringUtils.isNotEmpty(value)) {
                    // 设置请求头
                    requestTemplate.header(name, ImmutableList.of(value));
                    log.debug("【前置通知】feign远程调用接口 {} 时，传递请求头参数 `{}` 的值是 {}", url, name, value);
                } else {
                    log.debug("【前置通知】feign远程调用接口 {} 时，忽略请求头参数 `{}`", url, name);
                }
            }

            /* 参数转发
            requestTemplate.query(paramName, paramValue);*/
        } catch (Exception e) {
            log.error("【前置通知】调用微服务({})时执行`自定义Feign请求拦截器`发生异常:{}", url, e.getMessage(), e);
        }
    }

    @Override
    public void returnAdvice(Response response, Request.Options options) {
        String url = response.request().url();
        try {
            HttpServletResponse resp = ServletHelper.getResponse();

            // 注意，此处不该使用异步重写响应头，否则会导致响应头参数传递失败的情况发生
            // 获取 在FeignClient调用过程中 需要被传递 的 HTTP响应头参数
            for (String name : feignHeaderProperty.getHeaders()) {
                String value = FeignClientAdvice.readResponseHeader(response, name);
                // 执行feign远程调用结束之后：
                // （1）获取到Feign回传响应头中的参数值必须有效
                // （2）当前HTTP请求对象的响应头中，该参数必须是无效的
                String oldValue = resp.getHeader(name);
                if (StringUtils.isNotEmpty(value) && StringUtils.isEmpty(oldValue)) {
                    resp.setHeader(name, value);
                    log.debug("【返回通知】feign远程调用接口 {} 结束，获取响应头参数 `{}` 的值是 {}", url, name, value);
                } else {
                    String msg = StringUtils.isEmpty(value)
                            ? "【返回通知】feign远程调用接口 {} 结束，忽略值为空的响应头参数 `{}`"
                            : "【返回通知】feign远程调用接口 {} 结束，忽略响应头参数 `{}` ，原因：当前HTTP对象的响应头已存在值为 " + oldValue + " 的同名参数";
                    log.debug(msg, url, name);
                }
            }
        } catch (Exception e) {
            log.error("【返回通知】调用微服务({})时执行`自定义Feign响应拦截器`发生异常:{}", url, e.getMessage(), e);
        }
    }

}
